/**
 * Copyright (c) 2010-2011 Pierre Labatut
 *
 * This file is part of Space-Chronicles project.
 *
 * Space-Chronicles is free software: you can redistribute it and/or modify it
 * under the terms of its license which may be found in the accompanying
 * LICENSE file or <http://code.google.com/p/space-chronicles/>.
 */

package space.model;

import space.gl.RenderContext;
import java.nio.IntBuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import space.math.Vector3f;
import jinngine.physics.Body;
import space.stream.StructureInput;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import javax.media.opengl.GL3bc;
import jinngine.geometry.ConvexHull;
import jinngine.math.Matrix3;
import jinngine.math.Vector3;
import space.math.Vector2f;
import static space.math.Vector2f.*;

/**
 *
 * @author Pierre
 */
public class MdlMeshArray implements MdlGeometry, Drawable {

    final String name;
    final Vector3f[] vertexPosition;
    final Vector2f[] vertexUv;
    final Vector3f[] vertexNormal;
    final Vector3f[] vertexTangent;
    final Vector3f[] vertexBinormal;
    final int[] triangleIndices;

    public MdlMeshArray(final StructureInput si) {
        name = si.readString();
        vertexPosition = si.readArray(Vector3f.class);
        vertexNormal = si.readArray(Vector3f.class);
        vertexUv = si.readArray(Vector2f.class);
        triangleIndices = si.readIntArray();
        vertexTangent = new Vector3f[vertexPosition.length];
        vertexBinormal = new Vector3f[vertexPosition.length];
        buildTBN(triangleIndices,
                Arrays.asList(vertexPosition),
                Arrays.asList(vertexUv),
                Arrays.asList(vertexNormal));
    }

    public MdlMeshPack pack() {
        final ByteBuffer vertex_T2F_N3F_V3F = ByteBuffer.allocateDirect(
                vertexPosition.length * 14 * Float.SIZE / Byte.SIZE);
        vertex_T2F_N3F_V3F.order(ByteOrder.nativeOrder());
        final FloatBuffer floatBuffer = vertex_T2F_N3F_V3F.asFloatBuffer();
        for (int i = 0; i < vertexPosition.length; i++) {
            final Vector2f uv = vertexUv[i];
            floatBuffer.put(uv.x());
            floatBuffer.put(uv.y());
            final Vector3f tangent = vertexTangent[i];
            floatBuffer.put(tangent.x);
            floatBuffer.put(tangent.y);
            floatBuffer.put(tangent.z);
            final Vector3f binromal = vertexBinormal[i];
            floatBuffer.put(binromal.x);
            floatBuffer.put(binromal.y);
            floatBuffer.put(binromal.z);
            final Vector3f normal = vertexNormal[i];
            floatBuffer.put(normal.x);
            floatBuffer.put(normal.y);
            floatBuffer.put(normal.z);
            final Vector3f vertex = vertexPosition[i];
            floatBuffer.put(vertex.x);
            floatBuffer.put(vertex.y);
            floatBuffer.put(vertex.z);
        }
        final ByteBuffer indices = ByteBuffer.allocateDirect(
                triangleIndices.length * Integer.SIZE / Byte.SIZE);
        indices.order(ByteOrder.nativeOrder());
        final IntBuffer intBuffer = indices.asIntBuffer();
        intBuffer.put(triangleIndices);
        return new MdlMeshPack(name, vertex_T2F_N3F_V3F, indices);
    }

    private void buildTBN(final int[] sTri, final List<Vector3f> points,
            final List<Vector2f> texture, final List<Vector3f> normals) {

        if (sTri.length % 3 != 0) {
            throw new IllegalArgumentException();
        }
        if (points.size() != texture.size() || normals.size() != texture.size()) {
            throw new IllegalArgumentException();
        }

        final int nbVertex = points.size();
        final Vector3f[] tan1 = new Vector3f[nbVertex];
        final Vector3f[] tan2 = new Vector3f[nbVertex];
        for (int i = 0; i < tan1.length; i++) {
            tan1[i] = new Vector3f();
            tan2[i] = new Vector3f();
        }
        {
            final Vector3f t = new Vector3f();
            final Vector3f q1 = new Vector3f();
            final Vector3f q2 = new Vector3f();
            final Vector2f uv1 = new Vector2f();
            final Vector2f uv2 = new Vector2f();
            for (int tpos = 0; tpos < sTri.length;) {
                /*
                 * Suppose that we have a triangle whose vertex positions are given by the points
                 * P0, P1, and P2, and whose corresponding texture coordinates are given by (u0, v0),
                 * (u1, v1), and (u2, v2). Our calculations can be made much simpler by working relative
                 * to the vertex P0, so we let
                 * Q1 = P1 − P0
                 * Q2 = P2 − P0
                 */
                int i0 = sTri[tpos++];
                int i1 = sTri[tpos++];
                int i2 = sTri[tpos++];
                final Vector3f p0 = points.get(i0);
                q1.assign(points.get(i1)).assignSub(p0);
                q2.assign(points.get(i2)).assignSub(p0);
                final Vector2f t0 = texture.get(i0);
                uv1.assign(texture.get(i1)).assignSub(t0);
                uv2.assign(texture.get(i2)).assignSub(t0);
                float r = 1.0F / det(uv1, uv2);
                assert Float.isNaN(r) == false;
                if (Float.isInfinite(r) == false) {
                    t.assign((uv2.y() * q1.x - uv1.y() * q2.x) * r,
                            (uv2.y() * q1.y - uv1.y() * q2.y) * r,
                            (uv2.y() * q1.z - uv1.y() * q2.z) * r);
                    tan1[i0].assignAdd(t);
                    tan1[i1].assignAdd(t);
                    tan1[i2].assignAdd(t);
                    t.assign((uv1.x() * q2.x - uv2.x() * q1.x) * r,
                            (uv1.x() * q2.y - uv2.x() * q1.y) * r,
                            (uv1.x() * q2.z - uv2.x() * q1.z) * r);
                    tan2[i0].assignAdd(t);
                    tan2[i1].assignAdd(t);
                    tan2[i2].assignAdd(t);
                } else {
                    System.out.println(
                            "The face " + (tpos / 3 - 1) + " has a degenerated texture projection");
                }
            }
        }


        {
            final Vector3f t = new Vector3f();
            final Vector3f n = new Vector3f();
            for (int a = 0; a < nbVertex; a++) {
                final Vector3f tn = normals.get(a);
                n.assign(tn);
                final Vector3f pt = tan1[a];

                //Orthogonalize tangent regarding to normal
                n.assignMultiply(n.dot(pt));
                pt.assignSub(n);
                if (!pt.isEpsilon(1e-14f)) {
                    pt.assignNormalize();
                    vertexTangent[a] = pt;
                } else {
                    vertexTangent[a] = new Vector3f();
                }
                //Orthogonalize binormal regarding to normal
                final Vector3f pb = tan2[a];
                n.assign(tn);
                n.assignMultiply(n.dot(pb));
                pb.assignSub(n);
                t.assign(pt);
                t.assignMultiply(t.dot(pb));
                pb.assignSub(t);
                if (!pb.isEpsilon(1e-14f)) {
                    pb.assignNormalize();
                    vertexBinormal[a] = pb;
                } else {
                    vertexBinormal[a] = new Vector3f();
                }
            }
        }
    }

    /**
     * Does notfinh because it hold no harware resources
     * @param gl
     */
    public void dispose(GL3bc gl) {
    }

    public void addGeometry(Body body) {
        final ArrayList<Vector3> res = new ArrayList<Vector3>();
        for (Vector3f v : vertexPosition) {
            res.add(new Vector3(v.x, v.y, v.z));
        }
        body.addGeometry(new Matrix3(), new Vector3(), new ConvexHull("meshhull", res, 0.));

    }

    public Drawable optimize(GL3bc gl) {
        return pack();
    }

    public void draw(RenderContext rc) {

        final GL3bc gl = rc.gl();
        
        gl.glBegin(gl.GL_LINES);
        for (int i = 0; i < vertexPosition.length; i++) {
            final Vector3f v = vertexPosition[i];
            final float s = (float)(.025*  rc.glInvertScale(new Vector3(v.x, v.y, v.z)));
            final Vector3f t = vertexTangent[i];
            gl.glColor3f(1.f, 0.f, 0.f);
            gl.glVertex3f(v.x, v.y, v.z);
            gl.glVertex3f(v.x + t.x * s, v.y + t.y * s, v.z + t.z * s);

            final Vector3f b = vertexBinormal[i];
            gl.glColor3f(0.f, 1.f, 0.f);
            gl.glVertex3f(v.x, v.y, v.z);
            gl.glVertex3f(v.x + b.x * s, v.y + b.y * s, v.z + b.z * s);

            final Vector3f n = vertexNormal[i];
            gl.glColor3f(0.f, 0.f, 1.f);
            gl.glVertex3f(v.x, v.y, v.z);
            gl.glVertex3f(v.x + n.x * s, v.y + n.y * s, v.z + n.z * s);
        }
        gl.glEnd();
    }

   
}
